//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2024-03-13     ZhangYihua   first version
//
// Description  : 
//################################################################################

// only support single master scenarious
module i2c_mst #(
parameter           CLK_DIV                 = 2,    // CLK_DIV>=2, frequency of scl = Fclk/2/CLK_DIV
parameter           LEN_BW                  = 4     // data byte number(exclude addr byte) for each read or write
) ( 
input                                       rst_n,
input                                       clk,

input                                       trans_req,  // must keep adr, rw, len unchanged while req is high
input               [7-1:0]                 trans_adr,  // slave device address
input                                       trans_rw,   // 1'b0:write to slave; 1'b1:read from slave
input               [LEN_BW-1:0]            trans_len,  // data byte number(exclude addr byte) for each read or write
output  reg                                 trans_ack,
output  reg                                 trans_fail, // alave NAK detected

output                                      tx_rdy,     // take one byte for TX
output              [LEN_BW-1:0]            tx_seq,
input               [8-1:0]                 tx_data,

output                                      rx_vld,     // get one byte from RX
output              [LEN_BW-1:0]            rx_seq,
output              [8-1:0]                 rx_data,

// I2C signals, implement open drain IO in top-level module
// pull up SCL and SDA by resistance in PCB
input                                       scl_in,
output  reg                                 scl_od,     // 1'b0:output to GND; 1'b1:output to high-z
input                                       sda_in,
output  reg                                 sda_od      // 1'b0:output to GND; 1'b1:output to high-z
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          CLK_DIV_BW              = $clog2(CLK_DIV);
localparam [CLK_DIV_BW-1:0] CLK_DIV_FULL    = CLK_DIV-1;
localparam [CLK_DIV_BW-1:0] CLK_DIV_HALF    = CLK_DIV/2-1;

localparam [LEN_BW-1:0]     BYTE_TWO        = 2;
localparam [LEN_BW-1:0]     BYTE_ONE        = 1;

localparam          ST_IDLE                 = 3'b000;
localparam          ST_START                = 3'b001;
localparam          ST_CLK_L                = 3'b010;
localparam          ST_CLK_H                = 3'b011;
localparam          ST_STOP1                = 3'b100;
localparam          ST_STOP2                = 3'b101;
localparam          ST_CLK_AL               = 3'b110;

reg                                         scl_1d;
reg                 [CLK_DIV_BW-1:0]        cnt_div;
wire                                        half_en;
wire                                        full_en;
reg                 [3-1:0]                 cur_st;
reg                 [3-1:0]                 nxt_st;
reg                 [4-1:0]                 cnt_bit;
wire                                        byte_done;
reg                                         adr_ind;
reg                 [LEN_BW-1:0]            cnt_byte;
wire                                        last_data_ind;
reg                                         trans_done;
wire                                        rdat_ack;
reg                 [9-1:0]                 dat_shift;
reg                 [LEN_BW-1:0]            cnt_seq;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_div <=`U_DLY {CLK_DIV_BW{1'b0}};
    end else begin
        if (cnt_div>=CLK_DIV_FULL) begin
            cnt_div <=`U_DLY {CLK_DIV_BW{1'b0}};
        end else begin
            cnt_div <=`U_DLY cnt_div + 1'd1;
        end
    end
end

assign half_en = (cnt_div==CLK_DIV_HALF) ? 1'b1 : 1'b0;
assign full_en = (cnt_div>=CLK_DIV_FULL) ? 1'b1 : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        scl_1d <=`U_DLY 1'b1;
    end else if (half_en==1'b1) begin
        scl_1d <=`U_DLY scl_in;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cur_st <=`U_DLY ST_IDLE;
    end else if (full_en==1'b1) begin
        cur_st <=`U_DLY nxt_st;
    end
end

always@(*) begin
    case(cur_st)
        ST_IDLE :   // SCL=1'b1, SDA=1'b1
            if (trans_req==1'b1 && trans_ack==1'b0 && scl_in==1'b1 && sda_in==1'b1)
                nxt_st = ST_START;
            else
                nxt_st = ST_IDLE;

        ST_START :  // SCL=1'b1, SDA fall to 1'b0
            if (scl_in==1'b1)
                nxt_st = ST_CLK_L;
            else
                nxt_st = ST_STOP1;

        ST_CLK_L :  // SCL=1'b0, launch SDA with new bit
//            if (trans_done==1'b1 || trans_fail==1'b1)
            if (trans_done==1'b1)
                nxt_st = (sda_in==1'b0) ? ST_STOP1 : ST_CLK_AL;
            else
                nxt_st = ST_CLK_H;

        ST_CLK_H :  // SCL=1'b1, keep SDA unchanged
            if (scl_1d==1'b1)       // high width of SCL is enough
                nxt_st = ST_CLK_L;
            else
                nxt_st = ST_CLK_H;

        ST_STOP1 :   // SCL=1'b1, SDA=1'b0
            if (scl_1d==1'b1)
                nxt_st = ST_STOP2;
            else
                nxt_st = ST_STOP1;

        ST_STOP2 :   // SCL=1'b1, SDA rise to 1'b1
            if (sda_in==1'b1)
                nxt_st = ST_IDLE;
            else
                nxt_st = ST_CLK_AL;

        // ST_CLK_AL
        default :   // SCL=1'b0, SDA=1'b0
            nxt_st = ST_STOP1;
    endcase
end

assign clk_h_en = (cur_st==ST_CLK_H && scl_1d==1'b1) ? 1'b1 : 1'b0;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_bit <=`U_DLY 4'd0;
    end else if (full_en==1'b1) begin
        if (cur_st==ST_IDLE)
            cnt_bit <=`U_DLY 4'd0;
        else if (clk_h_en==1'b1) begin
            cnt_bit <=`U_DLY (cnt_bit>=4'd8) ? 4'd0 : cnt_bit + 1'd1;
        end
    end
end

assign byte_done = (clk_h_en==1'b1 && cnt_bit>=4'd8) ? 1'b1 : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        adr_ind <=`U_DLY 1'b0;
    end else if (full_en==1'b1) begin
        if (cur_st==ST_START)
            adr_ind <=`U_DLY 1'b1;      // first byte must be Address+R/W byte
        else if (byte_done==1'b1) begin
            adr_ind <=`U_DLY 1'b0;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_byte <=`U_DLY {LEN_BW{1'b0}};
    end else if (full_en==1'b1) begin
        if (cur_st==ST_START)
            cnt_byte <=`U_DLY trans_len;
        else if (adr_ind==1'b0 && byte_done==1'b1) begin
            cnt_byte <=`U_DLY cnt_byte - 1'd1;
        end
    end
end

assign last_data_ind = (adr_ind==1'b0 && cnt_byte==BYTE_ONE) ? 1'b1 : 1'b0;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        trans_done <=`U_DLY 1'b0;
    end else if (full_en==1'b1) begin
        trans_done <=`U_DLY byte_done & last_data_ind;
    end
end

assign rdat_ack = (adr_ind==1'b1 && cnt_byte==BYTE_ONE) ? 1'b1 :    // NAK for last rdata
                  (adr_ind==1'b0 && cnt_byte==BYTE_TWO) ? 1'b1 :    // NAK for last rdata
                                                          1'b0;     // ACK for others
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        dat_shift <=`U_DLY {8'h80, 1'b0};
    end else if (full_en==1'b1) begin
        if (cur_st==ST_START)
            dat_shift <=`U_DLY {trans_adr, trans_rw, 1'b1};     // slave ACK
        else if (cur_st==ST_STOP2 || cur_st==ST_IDLE)
            dat_shift <=`U_DLY {8'h80, 1'b0};
        else if (byte_done==1'b1) begin
            dat_shift <=`U_DLY (last_data_ind==1'b1) ? {8'h00, 1'b0} :      // force sda=0 before STOP
                               (trans_rw==1'b0)      ? {tx_data, 1'b1} :    // release sda for slave ACK
                                                       {8'hff, rdat_ack};   // master ACK/NAK
        end else if (clk_h_en==1'b1) begin
            dat_shift[8:1] <=`U_DLY dat_shift[7:0];
            if (adr_ind==1'b1 || trans_rw==1'b0)
                dat_shift[0] <=`U_DLY 1'b0;
            else
                dat_shift[0] <=`U_DLY sda_in;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        sda_od <=`U_DLY 1'b1;
    end else if (half_en==1'b1) begin // sda_od is later than scl_od
        if (cur_st==ST_IDLE || cur_st==ST_STOP2)
            sda_od <=`U_DLY 1'b1;
        else if (cur_st==ST_START || cur_st==ST_STOP1 || cur_st==ST_CLK_AL || trans_done==1'b1)
            sda_od <=`U_DLY 1'b0;
        else
            sda_od <=`U_DLY dat_shift[8];
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        scl_od <=`U_DLY 1'b1;
    end else if (full_en==1'b1) begin
        if (nxt_st==ST_CLK_L || nxt_st==ST_CLK_AL)
            scl_od <=`U_DLY 1'b0;
        else
            scl_od <=`U_DLY 1'b1;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        trans_ack <=`U_DLY 1'b0;
    end else begin
        if (cur_st==ST_STOP2 && nxt_st==ST_IDLE && full_en==1'b1)
            trans_ack <=`U_DLY trans_req;
        else
            trans_ack <=`U_DLY 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        trans_fail <=`U_DLY 1'b0;
    end else if (full_en==1'b1) begin
        if (cur_st==ST_START)
            trans_fail <=`U_DLY (scl_in==1'b1) ? 1'b0 : 1'b1;
        else if (byte_done==1'b1 && (adr_ind==1'b1 || trans_rw==1'b0) && sda_in==1'b1) begin
            trans_fail <=`U_DLY 1'b1;
        end
    end
end

assign tx_rdy = (~trans_rw) & byte_done & full_en & (~last_data_ind);
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_seq <=`U_DLY {LEN_BW{1'b0}};
    end else if (full_en==1'b1) begin
        if (cur_st==ST_IDLE)
            cnt_seq <=`U_DLY {LEN_BW{1'b0}};
        else if (tx_rdy==1'b1 || rx_vld==1'b1) begin
            cnt_seq <=`U_DLY cnt_seq + 1'd1;
        end
    end
end

assign tx_seq = cnt_seq;
assign rx_seq = cnt_seq;

assign rx_vld = trans_rw & byte_done & full_en & (~adr_ind);
assign rx_data = dat_shift[7:0];

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
